/**
 * Writed by yura and mak
 */

package wz.legend.handlers.trauma;

import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;

import org.apache.commons.digester.CallMethodRule;
import org.apache.commons.digester.Digester;
import org.apache.commons.digester.Rule;
import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;

import wz.legend.ActionState;
import wz.legend.Context;
import wz.legend.HttpContext;
import wz.legend.Status;
import wz.legend.handlers.ResponseHandler;
import wz.legend.handlers.farming.ItemManager;
import wz.legend.handlers.info.InfoConstants;
import wz.legend.handlers.info.RucksackInfoHandler;
import wz.legend.handlers.info.RucksackItem;
import wz.legend.handlers.info.User;
import wz.legend.handlers.steps.Step;
import wz.legend.handlers.steps.StepLoader;
import wz.legend.handlers.steps.StepMachine;
import wz.legend.handlers.steps.StepMachineImpl;
import wz.legend.util.ParseUtils;
import wz.legend.util.StepNullTimerTask;
import wz.legend.util.UriConstants;
import wz.legend.util.Utils;


public class TraumaHandler implements ResponseHandler {
    public static final String CONTEXT_KEY = "trauma";
    public static final String CONTEXT_SVITOK_PARAMS = "trauma.svitok.params";
    public static final String CONTEXT_ACTION_PARAMS = "trauma.action.params";

    private static final Logger LOG = Logger.getLogger(TraumaHandler.class);

    private static Map<Integer, List<String>> traumasMap;

    private int minUpdateTime = 5000;
    private int maxUpdateTime = 10000;
    private int updateTimeOut = 20000;
    private int stepsTimeout = 10000;
    private int nullRequestTime = 1000;

    private StepMachine stepMachine;
    private Timer timer;
    private TimerTask nullTimerTask;

    static {
        initTraumasMap();
    }

    private static void initTraumasMap() {
        try {
            Digester digester = new Digester();
            digester.addObjectCreate("traumas", HashMap.class);
            digester.addObjectCreate("traumas/trauma", "class", Object.class);
            digester.addCallMethod("traumas/trauma/item", "add", 1);
            digester.addCallParam("traumas/trauma/item", 0, "value");
            Rule r = new CallMethodRule(1, "put", 2, new Class[] {
                    Integer.class, Object.class });
            digester.addRule("traumas/trauma", r);
            digester.addCallParam("traumas/trauma", 0, "index");
            digester.addCallParam("traumas/trauma", 1, true);

            InputStream stream = ItemManager.class.getClassLoader()
                    .getResourceAsStream("traumatype.xml");
            traumasMap = (Map<Integer, List<String>>) digester.parse(stream);

        } catch (Exception e) {
            LOG.error(e, e);
            throw new RuntimeException(e);
        }
    }

    private void init() {
        InputStream stream = TraumaHandler.class.getClassLoader()
                .getResourceAsStream("traumasteps.xml");
        List<Step> steps = StepLoader.loadSteps(stream);
        stepMachine = new StepMachineImpl(steps);
        stepMachine.setTimeoutStepStart(stepsTimeout);
    };

    public TraumaHandler() {
    }

    public void handle(HttpContext httpContext, Context context) {
        if (Status.TRAUMA != context.getStatus()) {
            stopTimer();
            if (getTraumaContext(context) != null
                    && getTraumaContext(context).getState() == ActionState.ACTIVE) {
                stopProcess(context, false);
            }
            return;
        }

        synchronized (this) {
            if (timer == null) {
                startTimer(context);
            }

            if (getTraumaContext(context) == null) {
                init();
                context.addProperty(CONTEXT_KEY, new TraumaContext());
            }

            try {
                process(httpContext, context);
            } catch (Exception e) {
                LOG.error(e, e);
                context.setStatus(Status.INACTIVE);
            }

            if (getTraumaContext(context).getState() == ActionState.READY
                    && getTraumaContext(context).getNextUpdate() < System
                            .currentTimeMillis()) {
                update(context);
            }
        }
    }

    private void process(HttpContext httpContext, Context context)
            throws Exception {
        switch ((ActionState) getTraumaContext(context).getState()) {
        case READY:
            if (httpContext != null) {
                String uri = httpContext.getRequest().getRequestLine().getUri();
                if (uri.contains(UriConstants.CHAT_DATA_PHP)) {
                    String content = IOUtils.toString(httpContext.getResponse()
                            .getEntity().getContent(), "Windows-1251");
                    List<User> list = ParseUtils.parseUsers(content);
                    if (list.size() > 0) {
                        User user = list.get(0);
                        startProcess(context, user);
                    }
                }
            }
            break;
        case ACTIVE:
            stepMachine.handle(httpContext, context);
            if (stepMachine.isDone()) {
                stepMachine.reset();
                int count = getTraumaContext(context).getCount() - 1;
                if (count <= 0) {
                    stopProcess(context, true);
                } else {
                    getTraumaContext(context).setCount(count);
                    LOG.debug("next svitok");
                }
            }

            if (httpContext != null) {
                String uri = httpContext.getRequest().getRequestLine().getUri();
                if (uri.contains(UriConstants.ACTION_RUN_PHP)) {
                    String location = httpContext.getResponse().getFirstHeader(
                            "Location").getValue();
                    if (location.contains("failed=1")) {
                        context.getScriptStater().doScript("auction.error");
                        LOG.debug("svitok - bad");
                        stopProcess(context, true);
                    } else {
                        LOG.debug("svitok - ok");
                    }
                }
            }
            break;
        }
    }

    private void startTimer(Context context) {
        timer = new Timer();
        nullTimerTask = new StepNullTimerTask(this, context, Status.TRAUMA);
        timer.schedule(nullTimerTask, nullRequestTime, nullRequestTime);
        LOG.debug("nullTimer started");
    }

    private void stopTimer() {
        if (timer != null) {
            timer.cancel();
            timer = null;
            nullTimerTask.cancel();
            nullTimerTask = null;
            LOG.debug("nullTimer stoped");
        }
    }

    private void startProcess(Context context, User user) {
        getTraumaContext(context).setUser(user);
        getTraumaContext(context).setState(ActionState.ACTIVE);

        String trauma = user.getTrauma().toLowerCase();
        getTraumaContext(context).setType(getTraumaType(trauma));

        int count = user.getTime() / getTraumaDiv(trauma);
        RucksackItem item = getSvitokItem(context, getTraumaLevel(trauma));
        if (count > item.getCount())
            count = item.getCount();
        getTraumaContext(context).setCount(count);

        int index = getSvitokIndex(context, getTraumaLevel(trauma));
        context.addProperty(CONTEXT_SVITOK_PARAMS, new String[] { Integer
                .toString(index) });

        String[] params = new String[] {
                Integer.toString(getTraumaContext(context).getType()),
                "\"" + user.getName() + "\"" };
        context.addProperty(CONTEXT_ACTION_PARAMS, params);
        LOG.debug("trauma started " + user);
    }

    @SuppressWarnings("unchecked")
    private int getSvitokIndex(Context context, int level) {
        List<RucksackItem> list = (List<RucksackItem>) context
                .getProperty(InfoConstants.RUCKSACK_1);
        RucksackItem item = getSvitokItem(context, level);
        return list.indexOf(item);
    }

    @SuppressWarnings("unchecked")
    private RucksackItem getSvitokItem(Context context, int level) {
        List<RucksackItem> list = (List<RucksackItem>) context
                .getProperty(InfoConstants.RUCKSACK_1);
        for (int i = 0; i < list.size(); i++) {
            RucksackItem rucksackItem = list.get(i);
            if (RucksackInfoHandler.ITEM_SVITOK.equals(rucksackItem.getName())
                    && level == ((Integer) rucksackItem.getTag()).intValue()) {
                return rucksackItem;
            }
        }
        throw new RuntimeException("svitok index not found");
    }

    private int getTraumaLevel(String trauma) {
        for (Integer type : traumasMap.keySet()) {
            int index = traumasMap.get(type).indexOf(trauma);
            if (index >= 0) {
                return type;
            }
        }
        throw new RuntimeException("trauma index not found");
    }

    private int getTraumaType(String trauma) {
        for (Integer type : traumasMap.keySet()) {
            int index = traumasMap.get(type).indexOf(trauma);
            if (index >= 0) {
                return index;
            }
        }
        throw new RuntimeException("trauma index not found");
    }

    private int getTraumaDiv(String trauma) {
        for (Integer type : traumasMap.keySet()) {
            int index = traumasMap.get(type).indexOf(trauma);
            if (index >= 0) {
                switch (type) {
                case 1:
                    return 2;
                case 2:
                    return 4;
                }
            }
        }
        throw new RuntimeException("trauma div not found");
    }

    private void stopProcess(Context context, boolean update) {
        getTraumaContext(context).setState(ActionState.READY);
        getTraumaContext(context).setUser(null);
        getTraumaContext(context).setType(-1);
        getTraumaContext(context).setCount(-1);
        context.removeProperty(CONTEXT_SVITOK_PARAMS);
        stepMachine.reset();
        LOG.debug("trauma stoped");
        if (update)
            update(context);
    }

    private synchronized void update(Context context) {
        context.getScriptStater().doScript("trauma.refresh");
        long nextUpdate = Utils.getRandomInterval(minUpdateTime, maxUpdateTime);
        getTraumaContext(context).setNextUpdate(
                System.currentTimeMillis() + nextUpdate);
        LOG.debug("list updated. Next update after " + nextUpdate);
    }

    private TraumaContext getTraumaContext(Context context) {
        return (TraumaContext) context.getProperty(CONTEXT_KEY);
    }

    public int getMinUpdateTime() {
        return minUpdateTime;
    }

    public void setMinUpdateTime(int minUpdateTime) {
        this.minUpdateTime = minUpdateTime;
    }

    public int getMaxUpdateTime() {
        return maxUpdateTime;
    }

    public void setMaxUpdateTime(int maxUpdateTime) {
        this.maxUpdateTime = maxUpdateTime;
    }

    public int getUpdateTimeOut() {
        return updateTimeOut;
    }

    public void setUpdateTimeOut(int updateTimeOut) {
        this.updateTimeOut = updateTimeOut;
    }

    public int getStepsTimeout() {
        return stepsTimeout;
    }

    public void setStepsTimeout(int stepsTimeout) {
        this.stepsTimeout = stepsTimeout;
    }

    StepMachine getStepMachine() {
        return stepMachine;
    }

    void setStepMachine(StepMachine stepMachine) {
        this.stepMachine = stepMachine;
    }

    public int getNullRequestTime() {
        return nullRequestTime;
    }

    public void setNullRequestTime(int nullRequestTime) {
        this.nullRequestTime = nullRequestTime;
    }

}
